/*
 * Copyright (C) 2011-2013 Eric Lasota
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
#ifndef __RDX_UTILITY_HPP__
#define __RDX_UTILITY_HPP__

#include <cstddef>
#include "rdx_pragmas.hpp"
#include "rdx_platform.hpp"
#include "rdx_basictypes.hpp"

namespace RDX
{
	namespace Utility
	{
		enum BreakCause
		{
			BREAKCAUSE_Exception,
			BREAKCAUSE_UnusualCast,
			BREAKCAUSE_SentinelAuditFailed,
			BREAKCAUSE_UserBreak,
		};

		inline void DebugBreak(BreakCause cause)
		{
#ifndef NDEBUG
			__asm
			{
				int 3;
			}
#endif
		}

		struct IntermediateHash
		{
#ifdef RDX_HASH_MURMUR2
			UInt32 intermediate;
			UInt32 next;
			unsigned int byteOffset;
#endif
			IntermediateHash();
			void FeedBytes(const void *bytes, size_t sz);
			HashValue Flush();
		};

		inline HashValue HashBytes(const void *bytes, size_t sz)
		{
			IntermediateHash ihash;
			ihash.FeedBytes(bytes, sz);
			return ihash.Flush();
		}

		inline LargeInt HashValueIndex(HashValue hv, LargeInt maximum)
		{
			return static_cast<LargeInt>(static_cast<LargeUInt>(hv) % static_cast<LargeUInt>(maximum));
		}

		template<class _T>
		inline _T SwapValue(_T v)
		{
			if(sizeof(_T) == 1)
				return v;

			union
			{
				_T v;
				UInt8 bytes[1];
			} u;

			const UInt8 *inBytes = reinterpret_cast<const UInt8*>(&v);
			for(LargeInt i=0;i<sizeof(_T);i++)
				u.bytes[sizeof(_T)-1-i] = inBytes[i];
			return u.v;
		}

		inline LargeInt EncodeUTF8Char(Char c, UInt8 *output)
		{
			UInt32 u32char = (static_cast<UInt32>(c) & (static_cast<UInt32>(0xffffffff) >> (8*(4-sizeof(Char)))));
			u32char &= 0x7fffffff;	// Top bit being set is invalid

			LargeInt extraBytes = 0;
			UInt8 firstByte;

			if(u32char & 0xfc000000)		// 31
			{
				firstByte = static_cast<UInt8>(0xfc | (u32char >> 30));
				extraBytes = 5;
			}
			else if(u32char & 0xffe00000)	// 26
			{
				firstByte = static_cast<UInt8>(0xf8 | (u32char >> 24));
				extraBytes = 4;
			}
			else if(u32char & 0xffff0000)	// 21
			{
				firstByte = static_cast<UInt8>(0xf0 | (u32char >> 18));
				extraBytes = 3;
			}
			else if(u32char & 0xfffff800)	// 16
			{
				firstByte = static_cast<UInt8>(0xe0 | (u32char >> 12));
				extraBytes = 2;
			}
			else if(u32char & 0xffffff80)	// 11
			{
				firstByte = static_cast<UInt8>(0xc0 | (u32char >> 6));
				extraBytes = 1;
			}
			else
			{
				firstByte = static_cast<UInt8>(u32char);
				extraBytes = 0;
			}

			output[0] = firstByte;

			LargeInt bitOffset = (extraBytes-1)*6;
			for(LargeInt i=0;i<extraBytes;i++,bitOffset-=6)
				output[i+1] = ((u32char >> bitOffset) & 0x3f) | 0x80;

			return extraBytes + 1;
		}

		inline Char DecodeUTF8Char(const void **pData, LargeInt *pAvailableBytes)
		{
#ifdef RDX_UNICODE_DISABLE_UTF8
			(*pAvailableBytes)--;
			const char *chars = *reinterpret_cast<const char **>(pData);
			*pData = chars + 1;
			return static_cast<Char>(*chars);
#else
			LargeInt availableBytes = *pAvailableBytes;
			const UInt8 *bytes = reinterpret_cast<const UInt8 *>(*pData);

			UInt8 firstByte = bytes[0];
			availableBytes--;

			LargeInt extraBytes;

			if ((firstByte & 0x80) == 0)			// 7
			{
				extraBytes = 0;
				firstByte &= 0x7f;
			}
			else if ((firstByte & 0xe0) == 0xc0)	// 11
			{
				extraBytes = 1;
				firstByte &= 0x1f;
			}
			else if ((firstByte & 0xf0) == 0xe0)	// 16
			{
				extraBytes = 2;
				firstByte &= 0xf;
			}
			else if ((firstByte & 0xf8) == 0xf0)	// 21
			{
				extraBytes = 3;
				firstByte &= 0x7;
			}
			else if ((firstByte & 0xfc) == 0xf8)	// 26
			{
				extraBytes = 4;
				firstByte &= 0x3;
			}
			else if ((firstByte & 0xfe) == 0xfc)	// 31
			{
				extraBytes = 5;
				firstByte &= 0x1;
			}
			else
				return CHAR_Invalid;

			if (availableBytes < extraBytes)
				return CHAR_Invalid;

			UInt32 finalValue = static_cast<UInt32>(firstByte << (extraBytes*6));
			LargeInt bitOffset = (extraBytes-1)*6;

			for(LargeInt i=0;i<extraBytes;i++,bitOffset-=6)
			{
				UInt8 v = bytes[i+1];
				if((v & 0xc0) != 0x80)
					return CHAR_Invalid;
				finalValue |= ((static_cast<UInt32>(v & 0x3f)) << bitOffset);
			}

			if (finalValue >= (1 << (sizeof(Char)*8)))
				return CHAR_Invalid;	// Too big

			*pData = (bytes + extraBytes + 1);
			*pAvailableBytes = (*pAvailableBytes) - 1 - extraBytes;

			return static_cast<Char>(finalValue);
#endif
		}

		inline ptrdiff_t Max(ptrdiff_t a, ptrdiff_t b)
		{
			return a > b ? a : b;
		}


		inline ptrdiff_t FieldOffset(const void *base, const void *field)
		{
			return (reinterpret_cast<const char*>(field)) - (reinterpret_cast<const char*>(base));
		}

		inline bool CheckAddOverflow(LargeInt a, LargeInt b)
		{
			// Signed
			if(b < 0)
				return (RDX_LARGEINT_MIN - b) <= a;
			return (RDX_LARGEINT_MAX - b) >= a;
		}

		inline bool CheckMulOverflow(LargeInt a, LargeInt b)
		{
			if(a == 0 || b == 0)
				return true;
			// Signed
			if(b < 0)
				return (RDX_LARGEINT_MIN / b) <= a;
			return (RDX_LARGEINT_MAX / b) >= a;
		}

		inline LargeInt PaddedSize(LargeInt v, LargeInt alignment, bool &overflowed)
		{
			LargeInt basePad = alignment - 1;
			if(!CheckAddOverflow(v, basePad))
			{
				overflowed = true;
				return 0;
			}
			v += basePad;
			overflowed = false;
			return v - (v % alignment);
		}
		
		inline LargeInt PaddedSize(LargeInt v, LargeInt alignment)
		{
			LargeInt basePad = alignment - 1;
			v += basePad;
			return v - (v % alignment);
		}
	}
}

#ifdef RDX_HASH_MURMUR2
#	include "rdx_murmur2.hpp"
#endif

#endif
